#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN MVC MODULE FOR THN WEB SERVER

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For use in commercial software, please obtain a commercial license from THN Solutions LLC.    

  + For use in free software, this library is licensed under GPL version 3. A summary of 
	GPLv3 is listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt 
	included with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 18-Apr-13 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Text;
using System.Reflection;

using Thn;
using Thn.Net.Http;
using Thn.Collections;
#endregion

namespace Thn.Web.Mvc
{
    /// <summary>
    /// Abstract base class for controller managers. This class provide controller management routines.
    /// </summary>
#if DEBUG || TESTING
    public
#else
    public
#endif
 abstract class ControllerManagerBase : IControllerManager
    {
        #region Fields
        StringDictionary<ControllerInvocationContext> mMethods = new StringDictionary<ControllerInvocationContext>();
        #endregion

        #region Properties

        #region Renderer
        private IViewRenderer mRenderer;
        /// <summary>
        /// Gets or sets the renderer responsible for rendering views.
        /// </summary>
        public IViewRenderer Renderer
        {
            get { return mRenderer; }
            set { mRenderer = value; }
        }
        #endregion
        
        #endregion

        #region Reflect Controller
        void ReflectController(object controller)
        {
            Type t = controller.GetType();

            //build controller name
            string controllerName = t.Name;
            int idx = controllerName.IndexOf("Controller");
            if (idx > 0)
            {
                controllerName = controllerName.Substring(0, idx);
            }
            #region Defensive tracing
            else
            {
                //tag not found, skip
            }
            #endregion

            controllerName = controllerName.ToLowerInvariant();

            //reflect all public methods
            MethodInfo[] mis = t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            foreach (var mi in mis)
            {
                //create new context
                ControllerInvocationContext ctx = new ControllerInvocationContext();
                ctx.Method = mi;
                ctx.Controller = controller;

                //process attributes to see if this method can be accepted
                bool accept = false;
                object[] atts = mi.GetCustomAttributes(true);
                foreach (var at in atts)
                {
                    if (at is GETAttribute)
                    {
                        ctx.RequestMethod = HttpRequestMethod.Get;
                        accept = true;
                    }
                    else if (at is POSTAttribute)
                    {
                        ctx.RequestMethod = HttpRequestMethod.Post;
                        accept = true;
                    }
                    else
                    {
                        //discard this method, do not publish it
                        accept = false;
                    }
                }

                if (accept)
                {
                    //build a unique id for the method
                    StringBuilder b = new StringBuilder();
                    b.AppendFormat("{0}-", ctx.RequestMethod); //Http Method
                    b.Append(controllerName + ".");
                    //b.Append(mi.Name + ":");
                    b.Append(mi.Name.ToLowerInvariant() + ":");

                    //add parameters name
                    ParameterInfo[] pis = mi.GetParameters();
                    ctx.Parameters = pis;
                    if (pis != null && pis.Length > 0)
                    {
                        for (int i = 0; i < pis.Length; i++)
                        {
                            ParameterInfo pi = pis[i];

                            //append separator if not the first item
                            if (i > 0) b.Append("+");
                            //b.Append(pi.Name);
                            b.Append(pi.Name.ToLowerInvariant());
                        }
                    }

                    ctx.Id = b.ToString();
                    mMethods[ctx.Id] = ctx;
                }
                #region Defensive tracing
                else
                {
                    //method is not to be published, skip
                }
                #endregion
            }
        }
        #endregion

        #region Register Controller
        /// <summary>
        /// Register a controller to be managed.
        /// </summary>
        public void RegisterController(IViewController controller)
        {
            ReflectController(controller);

            //set arguments
            controller.ViewRenderer = mRenderer;
        }
        #endregion

        #region On Find Method
        /// <summary>
        /// Find the appropriate method to invoke.
        /// </summary>
        /// /// <param name="httpContext">The context associated with current HTTP request.</param>
        /// <param name="controllerName">Name of the controller to be executed.</param>
        /// <param name="methodName">Name of the method to be executed.</param>
        /// <param name="args">Parameters to be passed to the method.</param>
        /// <returns>The result returned after executing the controller.</returns>
        protected virtual ControllerInvocationContext OnFindMethod(IHttpRequestHandlingContext httpContext, string controllerName, string methodName, params object[] args)
        {
            ControllerInvocationContext result = null;

            //build id
            string id = string.Format("{0}.{1}-{2}", controllerName, methodName, httpContext.Request.Method);

            //look up method
            result = mMethods[id];

            return result;
        }
        #endregion

        #region On Find Method - Parameters
        /// <summary>
        /// Find the appropriate method to invoke from a query.
        /// </summary>
        /// /// <param name="httpContext">The context associated with current HTTP request.</param>
        /// <param name="controllerName">Name of the controller to be executed.</param>
        /// <param name="methodName">Name of the method to be executed.</param>
        /// <param name="parameters">Name of parameters in format: param1+param2+param3...</param>
        /// <returns>The result returned after executing the controller.</returns>
        protected virtual ControllerInvocationContext OnFindMethod(IHttpRequestHandlingContext httpContext, string controllerName, string methodName, string parameters)
        {
            ControllerInvocationContext result = null;

            //build id
            string id = string.Format("{0}-{1}.{2}:{3}", httpContext.Request.Method, controllerName, methodName, parameters);

            //look up method
            result = mMethods[id];

            return result;
        }
        #endregion

        #region IControllerManager Members

        #region Execute - Direct
        ///// <summary>
        ///// Execute a controller's logic.
        ///// </summary>
        ///// <param name="httpContext">The context associated with current HTTP request.</param>
        ///// <param name="controllerName">Name of the controller to be executed.</param>
        ///// <param name="methodName">Name of the method to be executed.</param>
        ///// <param name="args">Parameters to be passed to the method.</param>
        ///// <returns>The result returned after executing the controller.</returns>
        //object IControllerManager.Execute(IHttpRequestHandlingContext httpContext, string controllerName, string methodName, params object[] args)
        //{
        //    object result = null;

        //    //look up context
        //    ControllerInvocationContext ctx = OnFindMethod(httpContext, controllerName, methodName, args);

        //    if (ctx != null)
        //    {
        //        #region Context exists -> invoke

        //        result = ctx.Method.Invoke(ctx.Controller, args);

        //        #endregion
        //    }
        //    else
        //    {
        //        #region Context does not exist -> fail

        //        throw new Exception(string.Format("Could not find controller: {0} method: {1}", controllerName, methodName));

        //        #endregion
        //    }

        //    return result;
        //}
        #endregion

        #region Execute
        /// <summary>
        /// Execute a controller's logic. The method and parameters shall be inferred from the provided query.
        /// </summary>
        /// <param name="httpContext">The context associated with current HTTP request.</param>
        /// <param name="controllerName">Name of the controller to be executed.</param>
        /// <param name="methodName">Name of the method to be executed.</param>
        /// <returns>The result returned after executing the controller.</returns>
        object IControllerManager.Execute(IHttpRequestHandlingContext httpContext, string controllerName, string methodName, UrlQuery query)
        {
            object result = null;
            controllerName = controllerName.ToLowerInvariant();
            methodName = methodName.ToLowerInvariant();

            #region Build parameter names

            //build parameter names if necessary
            string parameterNames = "";         
            int count = query.Count;
            if (count > 0)
            {
                for (int i = 0; i < count; i++)
                {
                    //append + if not first item
                    if (i > 0) parameterNames += "+";
                    parameterNames += query.Names[i];
                }
            }

            parameterNames = parameterNames.ToLowerInvariant();
            #endregion

            //look up context
            ControllerInvocationContext ctx = OnFindMethod(httpContext, controllerName, methodName, parameterNames);

            if (ctx != null)
            {
                #region Context exists -> invoke

                IViewController controller = (IViewController)ctx.Controller;
                controller.HttpContext = httpContext;

                #region Reconstruct parameters

                object[] args = null;
                if (count > 0)
                {
                    args = new object[count];
                    ParameterInfo pi;
                    string value;
                    for (int i = 0; i < count; i++)
                    {
                        pi = ctx.Parameters[i];
                        value = query.Values[i];
                        if (pi.ParameterType != typeof(string))
                        {
                            #region Not string type -> convert from string

                            args[i] = ConvertValue.To(value, pi.ParameterType);

                            #endregion
                        }
                        else
                        {
                            #region String type -> no conversion needed

                            args[i] = value;

                            #endregion
                        }
                    }
                }

                #endregion

                try
                {
                    //result = ctx.Method.Invoke(ctx.Controller, args);
                    result = ctx.Method.Invoke(controller, args);
                }
                catch(Exception ex)
                {
                    throw ex.GetInnerMost();
                }
                #endregion
            }
            else
            {
                #region Context does not exist -> skip so that other modules can handle next

                //throw new Exception(string.Format("Could not find controller: {0} method: {1}", controllerName, methodName));

                #endregion
            }

            return result;
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Create a new instance.
        /// </summary>
        public ControllerManagerBase()
            : base()
        { }
        #endregion
    }
}
